define([
    'app',
    'json5',
    'mock',
    'base64',
    'jquery.form',
    'jquery.format',
    'ace-language-tools'
], function(app, JSON5, Mock, Base64) {
    app.controller('HttpRequesterController', function($scope, $compile, $http, $timeout, xDialog, xUtil, xLocation,
            toastr, $routeParams, cfpLoadingBar) {
        var self = this;
        self.url = '';
        self.cors = false;
        self.method = 'GET';
        self.cookies = '';
        self.sendType = 'send';
        self.completed = true;
        self.activeTab = 0;
        self.autowrap = true;
        self.collapsed = false;
        self.fullScreen = false;

        self.authType = '';
        self.authUser = '';
        self.authPass = '';
        self.authToken = '';

        self.dataType = 'none';
        self.dataTypes = ['none', 'form-data', 'x-www-form-urlencoded', 'raw', 'binary'];

        self.rawDataType = {
            mode: 'text', value: '', label: 'Text'
        };
        self.rawDataTypes = [{
            mode: 'text', value: '', label: 'Text'
        }, {
            mode: 'text', value: 'text/plain', label: 'Text(text/plain)'
        }, {
            mode: 'hjson', value: 'application/json', label: 'JSON(application/json)'
        }, {
            mode: 'javascript', value: 'application/javascript', label: 'JavaScript(application/javascript)'
        }, {
            mode: 'xml', value: 'application/xml', label: 'XML(application/xml)'
        }, {
            mode: 'xml', value: 'text/xml', label: 'XML(text/xml)'
        }, {
            mode: 'html', value: 'text/html', label: 'HTML(text/html)'
        }];

        self.urlChanged = false;
        self.queryParamsCount = 0;
        self.headerParamsCount = 0;
        // parameters
        self.queryParams = [];
        self.headerParams = [];
        self.formParams = [];
        self.formDataParams = [];
        // response
        self.respActiveTab = 0;
        self.respStatus = 0;
        self.respBody = null;
        self.respHeaders = [];
        self.respCookies = [];

        var dataEditor = null,
            dataViewer = null;

        var dataParsable = true;
        self.dataParsable = true;

        var contentType = { name: 'Content-Type', checked: true, disabled: true },
            authorization = { name: 'Authorization', checked: true, disabled: true };

        var mockDocName = '';

        var ajaxRequest = null;

        self.init = function() {
            // Initialize
            $timeout(function() {
                // Mock.js snippets
                Mock.snippets(ace.require("ace/ext/language_tools"));
                // Data editor
                dataEditor = ace.edit('data-editor', {
                    mode: 'ace/mode/text',
                    theme: 'ace/theme/chrome',
                    fontSize: 14,
                    showPrintMargin: false,
                    autoScrollEditorIntoView: true,
                    enableBasicAutocompletion: true,
                    enableSnippets: true,
                    enableLiveAutocompletion: true
                });
                dataEditor.getSession().on('changeAnnotation', function(e) {
                    // Check errors
                    $timeout(function() {
                        var annotations = dataEditor.getSession().getAnnotations();
                        dataParsable = true;
                        if (!_.isEmpty(annotations)) {
                            if (_.find(annotations, function(annotation) {
                                return annotation.type == 'error';
                            })) {
                                dataParsable = false;
                            }
                        }
                        if (self.dataParsable != dataParsable) {
                            self.dataParsable = dataParsable;
                        }
                    });
                });

                // Data viewer
                dataViewer = ace.edit('data-viewer', {
                    mode: 'ace/mode/text',
                    theme: 'ace/theme/chrome',
                    fontSize: 14,
                    readOnly: true,
                    useWorker: false,
                    wrap: self.autowrap,
                    showPrintMargin: false,
                    highlightGutterLine: false,
                    highlightActiveLine: false,
                    autoScrollEditorIntoView: true
                });

                xUtil.clipboard.copy('#copy', function() {
                    return dataViewer.getValue();
                });

                $('#data-viewer .ace_scroller').append(
                        $compile($('#viewer-tools').html())($scope)
                    );
                $('#data-viewer .x-editor-tool').mousedown(function(e) { return false; });

                $scope.$watch('vm.fullScreen', function() {
                    setTimeout(function() {
                        dataViewer.resize();
                    }, 50);
                });

                $scope.$watch('vm.rawDataType', function(newValue) {
                    if (newValue) {
                        var modeId = 'ace/mode/' + newValue.mode;
                        if (modeId != dataEditor.getOption('mode')) {
                            dataEditor.getSession().setMode(modeId);
                        }
                    }
                });

                $scope.$watch('vm.respBody', function(newValue) {
                    if (newValue == null || newValue.data == null) {
                        dataViewer.setValue('', -1);
                    } else {
                        var modeId = 'ace/mode/' + newValue.mode;
                        if (modeId != dataViewer.getOption('mode')) {
                            dataViewer.getSession().setMode(modeId);
                        }
                        dataViewer.setValue(newValue.data, -1);
                    }
                    dataViewer.resize();
                });

                if ($routeParams.target) {
                    loadMockRequest();
                }
            });

            $scope.$watch('vm.url', function(newValue) {
                self.cors = /\/\/(127\.0\.0\.1)|(localhost)/.test(newValue);
            });
            $('input[name="url"]').change(function() {
                $timeout(function() {
                    $scope.$apply(function() {
                        self.urlChanged = true;
                        var location = getLocation();
                        var params = _.map(location.search(), function(value, name) {
                            return { name: name, value: value, checked: true };
                        });
                        params.push({
                            name: '',
                            value: '',
                            checked: true
                        });
                        self.queryParams = params;
                    });
                });
            });

            $scope.$watch('vm.queryParams', function(newValue) {
                if (newValue && !self.urlChanged) {
                    var location = getLocation();
                    var params = {};
                    _.forEach(newValue, function(param) {
                        if (param.checked && param.name != '') {
                            params[param.name] = param.value;
                        }
                    });
                    location.search(params);
                    self.url = location.url();
                } else if (self.urlChanged) {
                    self.urlChanged = !self.urlChanged;
                }
                self.queryParamsCount = _.size(_.filter(newValue, function(param) {
                    return param.checked && param.name != '';
                }));
            }, true);

            $scope.$watch('vm.headerParams', function(newValue) {
                self.headerParamsCount = _.size(_.filter(newValue, function(param) {
                    return param.checked && param.name != '';
                }));
            }, true);

            $scope.$watchGroup(['vm.dataType', 'vm.rawDataType'], function(newValue) {
                switch(newValue[0]) {
                    case 'binary':
                    case 'form-data':
                        contentType.value = 'multipart/form-data';
                        break;
                    case 'x-www-form-urlencoded':
                        contentType.value = 'application/x-www-form-urlencoded';
                        break;
                    case 'raw':
                        contentType.value = newValue[1].value;
                        break;
                    default:
                        contentType.value = '';
                }
                // Raw data
                self.dataParsable = dataParsable || newValue[0] != 'raw';
                // Header params
                var index = self.headerParams.indexOf(contentType);
                if (contentType.value == '') {
                    if (index != -1) {
                        self.headerParams.splice(index, 1);
                    }
                } else if (index == -1) {
                    self.headerParams.unshift(contentType);
                }
            });
        }

        function getLocation() {
            var url = self.url ? self.url : $('input[name="url"]').val();
            return xLocation.parse(url);
        }

        function loadMockRequest() {
            $http({
                method: 'POST',
                url: '/' + $routeParams.target + '?origin=true',
                transformResponse: function(response) {
                    return response;
                }
            }).then(function(response) {
                mockDocName = xUtil.regex.keyMatch(response.data, '标题');

                var path = xUtil.regex.urlMatch(response.data, $routeParams.target);
                if (/^http(s)?:\/\//.test(path)) {
                    self.url = path;
                } else {
                    self.url = xUtil.testhost.get() + path;
                }
                $('input[name="url"]').trigger('change');

                self.method = xUtil.regex.methodMatch(response.data, $routeParams.target);
                self.method = (self.method == '' ? 'GET' : self.method);
                self.activeTab = (self.method == 'GET' ? 0 : 3);

                try {
                    var headers = _.map(JSON5.parse(xUtil.regex.keyMatch(response.data, 'Headers') || '{}'),
                            function(value, name) {
                                return { name: name, value: value, checked: true, disabled: false };
                            });
                    headers.push({ name: '', value: '', checked: true, disabled: false });
                    self.headerParams = headers;
                } catch (e) {
                    toastr.error('解析【Headers】错误：' + e);
                }

                try {
                    var params = _.map(JSON5.parse(xUtil.regex.keyMatch(response.data, 'Query') || '{}'),
                            function(value, name) {
                                return { name: name, value: '', checked: true, disabled: false };
                            });
                    params.push({ name: '', value: '', checked: true, disabled: false });
                    self.queryParams = Mock.mock(params);
                } catch (e) {
                    toastr.error('解析【Query】错误：' + e);
                }

                var parameters = xUtil.regex.keyMatch(response.data, '入参');
                if (!_.isEmpty(parameters)) {
                    $timeout(function() {
                        self.dataType = 'raw';
                        var extname = $routeParams.target.substring($routeParams.target.lastIndexOf('.'));
                        if (extname == '.json') {
                            self.rawDataType = {
                                mode: 'hjson', value: 'application/json', label: 'JSON(application/json)'
                            };
                        } else if (extname == '.xml') {
                            self.rawDataType = {
                                mode: 'xml', value: 'application/xml', label: 'XML(application/xml)'
                            };
                        }
                        dataEditor.setValue(parameters, -1);
                        dataEditor.resize();
                    });
                }
            });
        }

        self.callback = function(height, width, refresh) {
            // Size of request editor
            $('.x-request .tab-pane').css('height', (height - 106) + 'px');
            $('.x-request .tab-pane .x-editor pre').css('min-height', (height - 143) + 'px');
            // Size of response editor
            $('.x-response .tab-pane').css('height', (height - 57) + 'px');
            $('.x-response .tab-pane .x-editor pre').css('min-height', (height - 57) + 'px');
            // Toolbar
            if (refresh) {
                $('.navbar-form').append($compile($('#opt-toolbar').html())($scope));
            }
        }

        self.reset = function() {
            self.url = '';
            self.cors = false;
            self.method = 'GET';
            self.cookies = '';
            self.completed = true;
            self.activeTab = 0;
            self.autowrap = true;
            self.collapsed = false;
            self.fullScreen = false;

            self.authType = '';
            self.authUser = '';
            self.authPass = '';
            self.authToken = '';

            self.dataType = 'none';
            self.rawDataType = {
                mode: 'text', value: '', label: 'Text'
            };

            // parameters
            self.queryParams = [{
                name: '',
                value: '',
                checked: true,
                disabled: false
            }];
            self.headerParams = [{
                name: '',
                value: '',
                checked: true,
                disabled: false
            }];
            self.formParams = [{
                name: '',
                value: '',
                checked: true,
                disabled: false
            }];
            self.formDataParams = [{
                name: '',
                type: 'text',
                value: '',
                checked: true,
                disabled: false
            }];
            // response
            self.respActiveTab = 0;
            self.respStatus = 0;
            self.respBody = null;
            self.respHeaders = [];
            self.respCookies = [];

            dataEditor.setValue('');
            self.dataParsable = true;
        }

        self.changeMethod = function(method) {
            if (method == 'GET' || method == 'HEAD') {
                self.activeTab = 0;
                self.dataParsable = true;
            } else {
                self.dataParsable = dataParsable;
            }
        }

        self.changeAuthValue = function() {
            var index = self.headerParams.indexOf(authorization);
            if (!self.authType) {
                if (index != -1) {
                    self.headerParams.splice(index, 1);
                }
            } else {
                var authValue = '';
                if (self.authType == 'Basic') {
                    authValue = Base64.encode(self.authUser + ':' + self.authPass);
                } else if (self.authType == 'Bearer') {
                    authValue = self.authToken.trim();
                }
                authorization.value = self.authType + ' ' + authValue;
                if (index == -1) {
                    self.headerParams.unshift(authorization);
                }
            }
        }

        var modalInstances = [];
        self.sendRequest = function(valid) {
            self.collapsed = false;
            self.respActiveTab = 0;
            self.respStatus = 0;
            self.respBody = null;
            self.respHeaders = [];
            self.respCookies = [];
            if (ajaxRequest != null) {
                ajaxRequest.abort();
                ajaxRequest = null;
            }

            // Send request
            if (valid) {
                self.completed = false;
                cfpLoadingBar.start();
                // Create form
                var jqForm = $('<form style="display:none">').attr('accept-charset', 'UTF-8');
                // clear request
                var clearRequest = function(cancelled) {
                    if (!self.completed) {
                        self.completed = true;
                        if (ajaxRequest != null) {
                            ajaxRequest.abort();
                            ajaxRequest = null;
                        }
                        var _clearViews = function() {
                            while (modalInstances.length > 0) {
                                modalInstances.shift().close();
                            }
                            jqForm.remove();
                            cfpLoadingBar.complete();
                        };
                        if (cancelled) {
                            _clearViews();
                        } else {
                            $timeout(_clearViews, 500);
                        }
                    }
                };

                var options = {
                    url: (self.cors ? self.url : '/mock/test/request'),
                    method: (self.cors ? self.method : 'POST'),
                    headers: {
                        'Pragma': 'no-cache',
                        'Cache-Control': 'no-cache'
                    },
                    iframe: (self.sendType == 'download'),
                    iframeSrc: '',
                    timeout: 905000,
                    dataType: 'text',
                    complete: function(jqXHR, textStatus) {
                        if (jqXHR && textStatus != 'abort') {
                            clearRequest();

                            self.respStatus = jqXHR.status;
                            self.respHeaders = parseHeaders(jqXHR.getAllResponseHeaders());
                            self.respCookies = parseCookies(jqXHR.getResponseHeader('x-set-cookies'));

                            if (jqXHR.status != 200 && _.isEmpty(jqXHR.responseText)) {
                                self.respStatus = 600;
                                if (self.sendType == 'download') {
                                    self.respBody = { mode: 'text', data: textStatus };
                                } else {
                                    self.respBody = { mode: 'text', data: jqXHR.statusText };
                                }
                            } else {
                                var contentType = jqXHR.getResponseHeader('Content-Type');
                                if (/\/json/.test(contentType)) {
                                    self.respBody = {
                                        mode: 'json',
                                        data: $.format(jqXHR.responseText, { method: 'json' })
                                    };
                                } else if (/\/xml/.test(contentType)) {
                                    self.respBody = {
                                        mode: 'xml',
                                        data: $.format(jqXHR.responseText, { method: 'xml' })
                                    };
                                } else if (/\/javascript/.test(contentType)) {
                                    self.respBody = { mode: 'javascript', data: jqXHR.responseText };
                                } else if (/\/html/.test(contentType)) {
                                    self.respBody = { mode: 'html', data: jqXHR.responseText };
                                } else {
                                    self.respBody = { mode: 'text', data: jqXHR.responseText };
                                }
                            }

                            $timeout(function() { $scope.$apply(); });
                        }
                    }
                };

                // parameters
                if (self.cors) {
                    angular.extend(options['headers'], JSON.parse(buildHeaders()));
                    delete options['headers']['Content-Type'];
                    // multipart form
                    if (['form-data', 'binary'].indexOf(self.dataType) != -1) {
                        jqForm.attr('enctype', 'multipart/form-data');
                    }
                } else {
                    jqForm.attr('enctype', 'multipart/form-data');
                    _.forEach([
                        { name: '__url', value: self.url },
                        { name: '__method', value: self.method },
                        { name: '__headers', value: buildHeaders() },
                        { name: '__cookies', value: buildCookies() }
                    ], function(param) {
                        jqForm.append($('<input type="hidden">').attr({
                            name: param.name,
                            value: param.value
                        }));
                    });
                }

                // body
                if (self.method != 'GET' && self.method != 'HEAD') {
                    switch(self.dataType) {
                        case 'form-data':
                        case 'x-www-form-urlencoded':
                            var fields = {};
                            var params = (self.dataType == 'form-data' ? self.formDataParams : self.formParams);
                            _.forEach(params, function(param) {
                                if (param.checked && param.name != '') {
                                    if (param.type == 'file') {
                                        jqForm.append($('#form-data input[name="' + param.name + '"]').clone());
                                    } else {
                                        fields[param.name] = param.value;
                                    }
                                }
                            });
                            if (self.cors) {
                                _.forEach(fields, function(value, name) {
                                    jqForm.append($('<input type="hidden">').attr({
                                        name: name,
                                        value: value
                                    }));
                                });
                            } else {
                                jqForm.append($('<textarea name="__body">').val(JSON.stringify(fields)));
                            }
                            break;
                        case 'raw':
                            var body = dataEditor.getValue();
                            if (self.rawDataType.value == 'application/json' && body != '') {
                                body = JSON.stringify(Mock.mock(JSON5.parse(body)));
                            }
                            if (self.cors) {
                                options['contentType'] = self.rawDataType.value;
                                options['data'] = body;
                            } else {
                                jqForm.append($('<textarea name="__body">').val(body));
                            }
                            break;
                        case 'binary':
                            jqForm.append($('#binary input[name="file"]').clone());
                            break;
                        default: break;
                    }
                }
                jqForm.appendTo(document.body);

                // Send request
                if (self.cors && self.sendType != 'download'
                    && ['form-data', 'x-www-form-urlencoded', 'binary'].indexOf(self.dataType) == -1) {
                    ajaxRequest = $.ajax(options);
                } else {
                    ajaxRequest = jqForm.ajaxSubmit(options);
                }

                // Release after 60s when download
                if (self.sendType == 'download') {
                    $timeout(clearRequest, 60000);
                }

                // Show cancel modal after 2 seconds
                setTimeout(function() {
                    if (!self.completed) {
                        xDialog.modal({
                            template: 
                                '<div class="modal-header">' +
                                '  <h4 class="modal-title">请求发送中</h4>' +
                                '</div>' +
                                '<div class="modal-body">' +
                                '  <p>服务器正在努力响应发送的请求，请稍候...</p>' +
                                '</div>' +
                                '<div class="modal-footer">' +
                                '  <button type="button" class="btn btn-primary" ng-click="cancel()">取消</button>' +
                                '</div>',
                            controller: ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
                                modalInstances.push($uibModalInstance);
                                // close modal when request is completed
                                if (self.completed) {
                                    $timeout(function() {
                                        while (modalInstances.length > 0) {
                                            modalInstances.shift().close();
                                        }
                                    });
                                }
                                $scope.cancel = function() {
                                    clearRequest(true);
                                    toastr.warning('发送的HTTP请求已取消！');
                                };
                            }]
                        });
                    }
                }, 2000);

                toastr.success('HTTP请求已发送！');
            }
        }

        self.showCode = function(valid, lang) {
            if (valid) {
                var name = mockDocName ? mockDocName : self.url;
                var params = {
                    name: name,
                    url: self.url,
                    method: self.method,
                    headers: JSON.parse(buildHeaders()),
                    cookies: JSON.parse(buildCookies())
                };
                // body
                if (self.method != 'GET' && self.method != 'HEAD') {
                    var data = {};
                    params.body = { type: self.dataType };
                    switch (self.dataType) {
                        case 'form-data':
                        case 'x-www-form-urlencoded':
                            var formData = (self.dataType == 'form-data' ? self.formDataParams : self.formParams);
                            _.forEach(formData, function(param) {
                                if (param.checked && param.name != '') {
                                    if (param.type == 'file') {
                                        data[param.name] = '@' + $('#form-data [name="' + param.name + '"]').val();
                                    } else {
                                        data[param.name] = param.value;
                                    }
                                }
                            });
                            break;
                        case 'raw':
                            var data = dataEditor.getValue();
                            if (self.rawDataType.value == 'application/json' && data != '') {
                                data = JSON.stringify(Mock.mock(JSON5.parse(data)));
                            }
                            break;
                        case 'binary':
                        default: break;
                    }
                    params.body.data = data;
                }
                // Show code snippet
                xDialog.open().showCode({ params: params, lang: lang });
            }
        }

        self.toggleAutowrap = function() {
            self.autowrap = !self.autowrap;
            dataViewer.setOption('wrap', self.autowrap);
        }

        self.showSearchbox = function() {
            dataViewer.execCommand('find');
        }

        self.toggleFoldall = function() {
            self.collapsed = !self.collapsed;
            if (self.collapsed) {
                dataViewer.execCommand('foldall');
            } else {
                dataViewer.execCommand('unfoldall');
            }
        }

        function buildHeaders() {
            var headers = {};
            _.forEach(self.headerParams, function(param) {
                if (param.checked && param.name != '') {
                    headers[param.name] = param.value;
                }
            });
            return JSON.stringify(headers);
        }

        function buildCookies() {
            var cookies = {};
            if (self.cookies) {
                var cookie = self.cookies.trim().replace(/\n/g, ';'),
                    pattern = /([^=]+)=([^;]+);?\s*/g,
                    match = null;
                while ((match = pattern.exec(cookie))) {
                    cookies[match[1]] = match[2];
                }
            }
            return JSON.stringify(cookies);
        }

        function parseHeaders(headersString) {
            var headers = {};
            if (headersString) {
                var rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/gm,
                    match = null;
                while ((match = rheaders.exec(headersString))) {
                    if (match[1] != 'x-set-cookies') {
                        headers[match[1]] = match[2];
                    }
                }
            }
            return _.map(headers, function(value, name) {
                return { name: name, value: value };
            });
        }

        function parseCookies(cookies) {
            return (cookies || '').split('@');
        }
    });
});
